﻿#ifndef ___PCLIB_S_XML___
#define ___PCLIB_S_XML___


#include "../m_core/m_core.hpp"

#include "pugixml-1.11/pugixml.hpp"

/**
*@brief  xml对象解析[DOM风格，将整个xml字符串在内存中构建成对象。接口简单易用，但不适合操作巨大的xml数据]  
*        在读写xml元素时，使用xpath的语法进行访问
*        xpath示例[!注意：数组下标从1开始]：
*          /key1           可以访问value1 : <key1>value1</key1>
*          /key1/key12     可以访问value12: <key1><key12>value12</key12></key1> 
*          /key1/key12[1]  可以访问value1 : <key1><key12>value1</key12><key12>value2</key12></key1> 
*@note  使用流程：
* CPCXmlObject xml;                                         //创建:xml对象
* auto result   = xml.Parse("<html>hello,world</html>");    //解析:使用xml对象解析xml字符串
* auto strvalue = xml.Get("/html","defalutvalue");          //读:节点值，失败返回默认值。
* auto intvalue = xml.Get("/html","default","attri");       //读:属性值，如果你关心错误原因，可调用LastErrorInfo()
* xml.Set("/html","newvalue");                              //写:节点值
* xml.Set("/html","newvalue","attri");                      //写:属性值。写入值可基于一个空xml，也可基于已经解析好的
* ret = xml.Stringify();                                    //转换:将xml对象再转换为xml字符串
*/
class CPCXmlObject : CPCNoCopyable
{
public:
    //解析和生成
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    /**
    *@brief     从字符串加载
    *@param     str[in]    xml字符串
    *@return    是否成功
    */
    CPCResult<> Parse(const char* str)
    {
        CPCResult<> result;
        if(NULL == str || str[0] == 0)
        {
            return result.SetFail("xml is empty.");
        }
        auto xmlret = m_doc.load(str,pugi::parse_full);
        if (!xmlret)
		{
            int offset = (int)xmlret.offset;
            return result.SetFail("load xml fail(offset:%d):%s.",offset,xmlret.description());
		}
        return result;
    }

    /**
    *@brief     从文件加载，会自动智能处理BOM
    *@param     path[in]    文件路径
    *@return    是否成功
    *@note      注意，如果文件是unicode，会将其解析到utf-8的DOM
    */
    CPCResult<> ParseFile(const char* path)
    {
        CPCResult<> result;
        if(NULL == path || path[0] == 0)
        {
            return result.SetFail("path is empty.");
        }
        auto xmlret = m_doc.load_file(path,pugi::parse_full,pugi::encoding_auto);
        if (!xmlret)
		{
            int offset = (int)xmlret.offset;
            return result.SetFail("load xml file(%s) fail(offset:%d):%s.",path, offset, xmlret.description());
		}
        return result;
    }

    /**
    *@brief     输出xml字符串
    *@param     bPretty[in]  是否格式化输出
    *@param     xpath[in]    对应的节点，比如可以将"/body"节点对应的value输出为字符串。如果未指定，默认输出整个xml串。
    *@return    是否成功，成功则结果在 StrResult1()
    */
    CPCResult<std::string> Stringify(bool bPretty = false, const char* xpath = NULL)
    {
        CPCResult<std::string> result;
        std::ostringstream ofs;
        if(xpath && xpath[0] != 0)
        {
            //保存子树
            auto xmlnode = m_doc.select_node(xpath).node();
            if(xmlnode.empty())
            {
                return result.SetFail("this node(%s) is not find",xpath);
            }
            xmlnode.print(ofs, PUGIXML_TEXT("\t"), bPretty?(pugi::format_indent):(pugi::format_raw));
        }
        else
        {
            //保存整个树
            m_doc.save(ofs, PUGIXML_TEXT("\t"), bPretty?(pugi::format_indent):(pugi::format_raw));
        }
        return result.SetSucc(ofs.str());
    }

    /**
    *@brief     输出xml文件
    *@param     path[in]     文件路径
    *@param     bPretty[in]  是否格式化输出
    *@param     xpath[in]    对应的节点，比如可以将"/body"节点对应的value输出为字符串。如果未指定，默认输出整个xml串。
    *@return    是否成功
    */
    CPCResult<> StringifyFile(const char* path, bool bPretty = false, const char* xpath = NULL)
    {
        CPCResult<> result;
        if(NULL == path || path[0] == 0)
        {
            return result.SetFail("path is empty.");
        }
        if(xpath && xpath[0] != 0)
        {
            //保存子树
            auto xmlnode = m_doc.select_node(xpath).node();
            if(xmlnode.empty())
            {
                return result.SetFail("this node(%s) is not find",xpath);
            }
            std::ofstream ofs;
            ofs.open(path,std::ios::binary|std::ios::out);
            if(!ofs.good())
            {
                ofs.close();
                return result.SetSystemFail();
            }
            xmlnode.print(ofs, PUGIXML_TEXT("\t"), bPretty?(pugi::format_indent):(pugi::format_raw));
            ofs.close();
        }
        else
        {
            //保存整个树
            m_doc.save_file(path, PUGIXML_TEXT("\t"), bPretty?(pugi::format_indent):(pugi::format_raw));
        }
        return result;
    }

public:
    //读取值
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    /**
    *@brief     如果你关心读写失败的错误原因，可以调用此函数 判断是否发生了读写值时的错误
    *@return    最后的读写时是否发生了错误。
    *@note      只有读写出错才需要调用
    */
    bool HasError() const
    {
        return !m_lastErrInfo.empty();
    }

    /**
    *@brief     如果你关心读写失败的错误原因，可以调用此函数 获取最后的错误原因
    *@return    最后的读写错误。如果为空代表无错误
    *@note      只有读写出错才会写ErrInfo，请及时获取，因为错误信息会在下一次Get/Set时清除
    */
    const char* LastErrorInfo() const
    {
        return m_lastErrInfo.c_str();
    }
    
    /**
    *@brief     获取值
    *@param     xpath[in]           节点xpath
    *@param     defaultvalue[in]    默认值，出错时返回默认值。调用HasGetError/LastGetErrorInfo获取错误
    *@param     attr[in]            要获取属性值，为空则表示获取子节点值而不是获取属性值
    *@return    结果
    */
    const char* Get(const char* xpath, const char* defaultvalue, const char* attr = NULL) 
    {
        m_lastErrInfo.clear();
        if(xpath == NULL)
        {
            m_lastErrInfo = "xpath == NULL ";
            return defaultvalue;
        }
        auto xmlnode = m_doc.select_node(xpath);
        if(!xmlnode)
        {
            m_lastErrInfo = CPCStrUtil::ShortFormat("this node(%s) is not find",xpath);
            return defaultvalue;
        }

        //获取value值
		if (attr == NULL || attr[0] == 0) 
		{
			return xmlnode.node().child_value();
		}
		else
		{
            auto pugi_node_attr_value = xmlnode.node().attribute(attr);
            if(!pugi_node_attr_value)
            {
			    m_lastErrInfo = CPCStrUtil::ShortFormat("attribute(%s) get fail.",attr);
                return defaultvalue;
		    }
			return pugi_node_attr_value.value();
        }
    }

public:
    //写入值
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    /**
    *@brief     写入值
    *@param     xpath[in]           节点xpath
    *@param     defaultvalue[in]    默认值，出错时返回默认值。调用HasGetError/LastGetErrorInfo获取错误
    *@param     attr[in]            要写入属性值的属性名，为空则表示写入子节点值而不是写入属性值
    *@return    是否成功
    */
    bool Set(const char* xpath, const char* value, const char* attr = NULL) 
    {
        m_lastErrInfo.clear();
        if(xpath == NULL || value == NULL)
        {
            m_lastErrInfo = "xpath == NULL || value == NULL";
            return false;
        }
        auto xmlnode = m_doc.select_node(xpath);
        if(!xmlnode && !this->XMLCreateNode(xpath,xmlnode))
        {
            return false;
        }

        //设置value值
		if (attr == NULL || attr[0] == 0) 
		{
			if(! xmlnode.node().text().set(value))
            {
                m_lastErrInfo = CPCStrUtil::ShortFormat("set value(%s->%s) fail.",xpath, value);
                return false;
            }
		}
		else
		{
            //判断属性key是否合法
            if(!CPCStrUtil::StrIsVariable(attr))
            {
                m_lastErrInfo = CPCStrUtil::ShortFormat("attribute(%s) is invalid.",attr);
                return false;
            }

            auto pugi_node_attr_value = xmlnode.node().attribute(attr);
            if(!pugi_node_attr_value)
            {
                pugi_node_attr_value = xmlnode.node().append_attribute(attr);
                if(!pugi_node_attr_value)
                {
			         m_lastErrInfo = CPCStrUtil::ShortFormat("doc.append_attribute(%s) fail.",attr);
                     return false;
		        }
            }
			if(! pugi_node_attr_value.set_value(value) )
            {
                m_lastErrInfo = CPCStrUtil::ShortFormat("set attribute([%s]%s->%s) fail.",xpath, attr, value);
                return false;
            }
		}
        return true;
    }

protected:
    /**
    *@brief     从前到后创建node，只支持简单的xpath(类似于/aa/bb/cc)
    *@param     xpath[in]           节点xpath
    *@param     pugi_node[in/out]   创建的节点node
    *@return    是否成功
    */
    bool XMLCreateNode(const char* xpath, pugi::xpath_node& pugi_node)
    {
        m_lastErrInfo.clear();
        size_t nodelen = strlen(xpath);
        if(xpath[0] != '/' || nodelen < 2 || nodelen >= MAX_PATH)
        {
            m_lastErrInfo = CPCStrUtil::ShortFormat("doc.select_node(%s) fail,only create simple xpath upported.",xpath);
            return false;
        }
        if(strstr(xpath,"[") != NULL || strstr(xpath,"]") != NULL || strstr(xpath,"@") != NULL )
        {
            m_lastErrInfo = CPCStrUtil::ShortFormat("doc.select_node(%s) fail,create node not support xpath with array or attribute.",xpath);
            return false;
        }
        char szNode[MAX_PATH] = {0};
        strcpy(szNode,xpath);

        pugi::xpath_node created_node;
        size_t lastSlash = 0;
        for(size_t i = 1 ; i < nodelen; i++)
        {
            if(xpath[i] != '/' && i != nodelen-1)
                continue;

            if(xpath[i] == '/' || i != nodelen-1)
                szNode[i] = 0;
            auto subnode = m_doc.select_node(szNode);
            if(!subnode)
            {
                const char* subNodeName = szNode + lastSlash + 1;
                if(created_node)
                    created_node = created_node.node().append_child(subNodeName);
                else
                    created_node = m_doc.append_child(subNodeName);
                if(!created_node)
                {
                    m_lastErrInfo = CPCStrUtil::ShortFormat("create node(%s) fail.",subNodeName);
                    return false;
                }
            }
            else
            {
                created_node = subnode;
            }
            szNode[i] = xpath[i];
            lastSlash = i;
        }
            
        //创建所有子节点后再尝试
        pugi_node = m_doc.select_node(xpath);
        if (!pugi_node)
        {
            m_lastErrInfo = CPCStrUtil::ShortFormat("doc.select_node(%s) fail after create all sub node.",xpath);
            return false;
        }
        return true;
    }

protected:
    pugi::xml_document m_doc;
    std::string m_lastErrInfo;
};


#endif    //___PCLIB_S_XML___
